#include <vector>
#include <iostream>
#include <array>
#include <chrono>

#include "nlohmann/json.hpp"

#include "xeus/xinput.hpp"
#include "xeus/xinterpreter.hpp"
#include "xeus/xhelper.hpp"

#include "xeus-xlang/xlang_interpreter.hpp"

#include "xeus-xlang/httplib.h"

#include <thread>
#include <atomic>
#include <sstream>

namespace nl = nlohmann;

int GetLocalValidPort()
{
    int startPort = 35000;
    int endPort = 36000;

    for (int port = startPort; port <= endPort; ++port)
    {
#if (WIN32)
        SOCKET testSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (testSocket == INVALID_SOCKET)
        {
            printf("Error creating socket.\n");
            WSACleanup();
            return -1;
        }

        sockaddr_in service;
        service.sin_family = AF_INET;
        service.sin_addr.s_addr = htonl(INADDR_ANY);
        service.sin_port = htons(port);

        if (bind(testSocket, (SOCKADDR *)&service, sizeof(service)) == 0)
        {
            closesocket(testSocket);
            return port;
        }

        closesocket(testSocket);
#else
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd == -1)
        {
            std::cerr << "Error creating socket." << std::endl;
            return -1;
        }

        sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = INADDR_ANY;
        addr.sin_port = htons(port);

        if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == 0)
        {
            close(sockfd);
            return port;
        }

        close(sockfd);
#endif
    }
    return -1;
}

namespace xeus_xlang
{

    void xlang_interpreter::execute_request_impl(xeus::xinterpreter::send_reply_callback cb,
                                                 int execution_counter,
                                                 const std::string &code,
                                                 xeus::execute_request_config config,
                                                 nl::json user_expressions)
    {
        std::string inCode(code);

        // connect to a xlang server
        std::string inCodeLower(code);
        //std::transform(inCodeLower.begin(), inCodeLower.end(), inCodeLower.begin(), ::tolower);
        size_t cmd_pos = inCodeLower.find("xlang_close");
        if (cmd_pos != std::string::npos)
        {
            close_xlang();
            cb(xeus::create_successful_reply());
            return;
        }

        cmd_pos = inCodeLower.find("xlang_connect");
        if (cmd_pos != std::string::npos)
        {
            connect_xlang(inCodeLower, cmd_pos, cb, execution_counter);
            cb(xeus::create_successful_reply());
            return;
        }

        cmd_pos = inCodeLower.find("run_command");
        if (cmd_pos != std::string::npos)
        {
            run_command(cb, execution_counter, inCodeLower);
            cb(xeus::create_successful_reply());
            return;
        }

        run_cell_code(cb, execution_counter, code);
        cb(xeus::create_successful_reply());
        return;
    }

    void xlang_interpreter::connect_xlang(std::string &inCodeLower, size_t cmd_pos, xeus::xinterpreter::send_reply_callback &cb, int execution_counter)
    {
        nl::json pub_data;
        inCodeLower.erase(std::remove_if(inCodeLower.begin(), inCodeLower.end(), ::isspace), inCodeLower.end());
        size_t start_pos = inCodeLower.find('(', cmd_pos + sizeof("xlang_connect") - 1);
        if (start_pos == std::string::npos)
        {
            publish_execution_error("CmdError", "1", {"xlang cmd not have '('"});
            cb(xeus::create_error_reply());
            return;
        }

        size_t end_pos = inCodeLower.find(')', start_pos + 1);
        if (end_pos == std::string::npos)
        {
            publish_execution_error("CmdError", "1", {"xlang cmd not have ')'"});
            cb(xeus::create_error_reply());
            return;
        }

        std::string addrNew = inCodeLower.substr(start_pos + 1, end_pos - start_pos - 1);
        if (addr == addrNew && isConnected)
        {
            pub_data["text/plain"] = "xlang server has connected";
            publish_execution_result(execution_counter, pub_data, nl::json::object());
        }
        else
        {
            if (isConnected && client)
            {
                isConnected = false;
                delete client;
            }

            int tryCount = 5;
            int tryTimes = 1;
            addr = addrNew;
            if (addr.empty()) // need create a local xlang server
            {
                int port = GetLocalValidPort();
                if (port < 0)
                {
                    publish_execution_error("CmdError", "1", {"can't find a valid port for xlang"});
                    cb(xeus::create_error_reply());
                    return;
                }
                create_xlang(cb, execution_counter, port);
                client = new httplib::Client("localhost", port);
            }
            else // connect to a running xlang server
                client = new httplib::Client(addr);

            client->set_read_timeout(100, 0); // 100sec for a long time running code

            std::string msg = "connecting to xlang server, try ";
            while (tryTimes <= tryCount)
            {
                pub_data["text/plain"] = msg + std::to_string(tryTimes);
                publish_execution_result(execution_counter, pub_data, nl::json::object());
                if (client->Get("/devops/checkStarted"))
                {
                    isConnected = true;
                    pub_data["text/plain"] = "connected to xlang server";
                    publish_execution_result(execution_counter, pub_data, nl::json::object());
                    break;
                }
                else
                {
                    pub_data["text/plain"] = "connecting to xlang server failed, try in 1 sec";
                    publish_execution_result(execution_counter, pub_data, nl::json::object());
                    std::this_thread::sleep_for(std::chrono::seconds(1));
                }
                ++tryTimes;
                if (tryTimes > tryCount)
                {
                    // pub_data["text/plain"] = "can not connect to xlang server";
                    publish_execution_error("ConnectError", "1", {"can not connect to xlang server"});
                    cb(xeus::create_error_reply());
                    return;
                }
            }
        }
    }

    void xlang_interpreter::run_command(xeus::xinterpreter::send_reply_callback &cb, int execution_counter, const std::string &command)
    {
        std::string trimmed = command.substr(command.find('(') + 1);
        trimmed = trimmed.substr(0, trimmed.find(')'));

        std::vector<std::string> args;
        std::stringstream ss(trimmed);
        std::string arg;

        while (std::getline(ss, arg, ',')) {
            arg.erase(0, arg.find_first_not_of(" \t"));
            arg.erase(arg.find_last_not_of(" \t") + 1);
            args.push_back(arg);
        }

        int timeOut = 0;
        if (args.size() > 1)
            timeOut = std::stoi(args[1]);

        std::string cmd = args[0];
        nl::json pub_data;
        std::string result;
#ifdef _WIN32
        // Windows-specific code to run the command and capture output
        HANDLE hPipeRead, hPipeWrite;
        SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES)};
        saAttr.bInheritHandle = TRUE; // Pipe handles are inherited
        saAttr.lpSecurityDescriptor = NULL;

        // Create a pipe for the child process's STDOUT.
        if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0))
        {
            std::cerr << "Failed to create pipe." << std::endl;
            publish_execution_error("RunCommandError", "1", {"Failed to create pipe."});
            cb(xeus::create_error_reply());
            return;
        }

        // Ensure the read handle to the pipe is not inherited.
        SetHandleInformation(hPipeRead, HANDLE_FLAG_INHERIT, 0);

        // Set up the process startup info.
        STARTUPINFOA si = {sizeof(STARTUPINFOA)};
        si.dwFlags = STARTF_USESTDHANDLES;
        si.hStdOutput = hPipeWrite;
        si.hStdError = hPipeWrite; // Redirect STDERR to STDOUT
        si.hStdInput = NULL;       // No input

        PROCESS_INFORMATION pi = {};

        // Create the child process.
        if (!CreateProcess(NULL, (LPSTR)cmd.c_str(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
        {
            std::cerr << "Failed to create process: " << cmd << " err_no: " <<  GetLastError() << std::endl;
            CloseHandle(hPipeWrite);
            CloseHandle(hPipeRead);
            publish_execution_error("RunCommandError", "1", {"Failed to create process: " + cmd});
            cb(xeus::create_error_reply());
            return;
        }

        // Close the write end of the pipe so that we can read from it.
        CloseHandle(hPipeWrite);

        // Wait for the process to finish
        DWORD waitResult = WaitForSingleObject(pi.hProcess, timeOut == 0 ? INFINITE : timeOut * 1000);
        if (waitResult == WAIT_TIMEOUT) {
            std::cerr << "Process timed out. Terminating." << std::endl;
            TerminateProcess(pi.hProcess, 1);
            pub_data["text/plain"] = "Process timed out. Terminated.";
            publish_execution_result(execution_counter, pub_data, nl::json::object());
        }

        // Read the output from the pipe.
        std::array<char, 128000> buffer;
        DWORD bytesRead;
        while (ReadFile(hPipeRead, buffer.data(), buffer.size(), &bytesRead, NULL) && bytesRead != 0)
        {
            result.append(buffer.data(), bytesRead);
        }

        // Close the handles.
        CloseHandle(hPipeRead);
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);

#else
        // Linux-specific code to run the command and capture output using popen()
        std::array<char, 128000> buffer;
        std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
        if (!pipe)
        {
            std::cerr << "popen() failed: " << cmd << std::endl;
            publish_execution_error("RunCommandError", "1", {"popen() failed: " + cmd});
            cb(xeus::create_error_reply());
            return;
        }

        // Read the output from the pipe and monitor for timeout
        auto start = std::chrono::steady_clock::now();
        while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
            result += buffer.data();

            if (timeOut > 0)
            {
                // Check for timeout
                auto end = std::chrono::steady_clock::now();
                std::chrono::duration<double> elapsed = end - start;
                if (elapsed.count() > timeOut) {
                    std::cerr << "Process timed out. Terminating." << std::endl;
                    pub_data["text/plain"] = "Process timed out. Terminated.";
                    publish_execution_result(execution_counter, pub_data, nl::json::object());
                    break;
                }
            }
        }

        // pclose() is called when the unique_ptr goes out of scope, ensuring the process ends
#endif
        pub_data["text/plain"] = result;
        publish_execution_result(execution_counter, pub_data, nl::json::object());
    }

    void xlang_interpreter::run_cell_code(xeus::xinterpreter::send_reply_callback &cb, int execution_counter, const std::string &code)
    {
        nl::json pub_data;
        if (!isConnected)
        {
            publish_execution_error("ConnectError", "1", {"connect a xlang server first"});
            cb(xeus::create_error_reply());
            return;
        }

        std::condition_variable cv;
        std::mutex cvMutex;
        bool finished = false;
        // start get print in thread
        std::thread get_print_thread([this, &cv, &finished, execution_counter, &cvMutex]()
                                     {
                client->Get("/devops/getprint",
                    [&](const char* data, size_t data_length) 
                    { // received print data
                        nl::json pub_data0;
                        pub_data0["text/plain"] = data;
                        publish_execution_result(execution_counter, pub_data0, nl::json::object()); // publish print data to jupyter
                        return true;
                    });
                {
                    std::lock_guard<std::mutex> locker(cvMutex);
                    finished = true;
                }
                cv.notify_one(); });

        httplib::Params params = {{"code", code}, {"exe_num", std::to_string(execution_counter)}};
        auto res = client->Post("/devops/runfragmentcode", params);
        {
            std::unique_lock<std::mutex> locker(cvMutex);
            cv.wait(locker, [&finished]{ return finished; });
        }

        if (get_print_thread.joinable())
        {
            get_print_thread.join();
            std::cout << "Thread has finished." << std::endl;
        }
        else
        {
            std::cerr << "Wait for thread failed!" << std::endl;
        }
        if (res)
        {
            pub_data["text/plain"] = "return value: " + res->body;
            publish_execution_result(execution_counter, pub_data, nl::json::object());
        }
        else
        {
            publish_execution_error("RunCodeLineError", "1", {"run code line on xlang server error"});
        }
    }

    void xlang_interpreter::configure_impl()
    {
        // Perform some operations
    }

    nl::json xlang_interpreter::complete_request_impl(const std::string &code,
                                                      int cursor_pos)
    {
        // Code starts with 'H', it could be the following completion
        // if (code[0] == 'H')
        //{
        //	return xeus::create_complete_reply({ "Hello", "Hey", "Howdy" }, 5, cursor_pos);
        //}
        //// No completion result
        // else
        {
            return xeus::create_complete_reply({}, cursor_pos, cursor_pos);
        }
    }

    nl::json xlang_interpreter::inspect_request_impl(const std::string &code,
                                                     int /*cursor_pos*/,
                                                     int /*detail_level*/)
    {
        nl::json result;

        // if (code.compare("print") == 0)
        //{
        //	return xeus::create_inspect_reply(true,
        //		{ "text/plain", "Print objects to the text stream file, [...]" });
        // }
        // else
        {
            return xeus::create_inspect_reply();
        }
    }

    nl::json xlang_interpreter::is_complete_request_impl(const std::string & /*code*/)
    {
        return xeus::create_is_complete_reply("complete");
    }

    nl::json xlang_interpreter::kernel_info_request_impl()
    {
        return xeus::create_info_reply(
            "5.3",
            "xlang_kernel",
            "0.1.0",
            "xlang",
            "0.1",
            "text/x-python",
            "x");
    }

    void xlang_interpreter::shutdown_request_impl()
    {

        std::cout << "xlang_kernel shutdown" << std::endl;
        close_xlang();
    }

    void xlang_interpreter::create_xlang(xeus::xinterpreter::send_reply_callback cb, int execution_counter, int port)
    {
        nl::json pub_data;
#if (WIN32)
        STARTUPINFO startup_info;

        memset(&startup_info, 0, sizeof(startup_info));
        startup_info.cb = sizeof(startup_info);

        std::string cmd;
        if (xlangPath.empty())
            cmd = exePath + "xlang/xlang.exe -event_loop -dbg -enable_python -port " + std::to_string(port);
        else
            cmd = xlangPath + " -event_loop -dbg -enable_python -port " + std::to_string(port);
        std::cerr << "start xlang server: " << cmd << std::endl;
        if (!CreateProcess(NULL, (LPSTR)cmd.c_str(), NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, (LPCSTR)exePath.c_str(), &startup_info, &process_info))
        {

            // pub_data["text/plain"] = "launch local xlang server failed";
            CloseHandle(process_info.hProcess);
            CloseHandle(process_info.hThread);
            publish_execution_error("CmdError", "1", {"launch local xlang server failed"});
            cb(xeus::create_error_reply());
            return;
        }
        else
        {
            xlangCreated = true;
            pub_data["text/plain"] = "local xlang server is starting";
            publish_execution_result(execution_counter, pub_data, nl::json::object());
        }
#else
        std::string cmd = xlangPath.empty() ? exePath + "xlang/xlang" : xlangPath;
        char *terminal_args[] = {
            // (char *)"gnome-terminal",
            // (char *)"--",
            (char *)cmd.c_str(),
            (char *)"-event_loop",
            (char *)"-dbg",
            (char *)"-enable_python",
            (char *)"-port",
            (char *)std::to_string(port).c_str(),
            nullptr};

        // Fork a new process
        process_id = fork();

        if (process_id == 0)
        {
            // Child process
            std::cerr << "start xlang server: " << cmd << std::endl;
            if (execvp(terminal_args[0], terminal_args) == -1)
            {
                std::cerr << "launch local xlang server failed: " << strerror(errno) << "  " << cmd << std::endl;
                std::string err("launch local xlang server failed: ");
                err += strerror(errno);
                publish_execution_error("CmdError", "1", {err});
                cb(xeus::create_error_reply());
                return;
            }
        }
        else if (process_id < 0)
        {
            // Fork failed
            std::cerr << "Failed to fork process: " << strerror(errno) << std::endl;
            std::string err("launch local xlang server failed: ");
            err += strerror(errno);
            publish_execution_error("CmdError", "1", {err});
            cb(xeus::create_error_reply());
            return;
        }
        else
        {
            xlangCreated = true;
            // Parent process
            pub_data["text/plain"] = "local xlang server is starting";
            publish_execution_result(execution_counter, pub_data, nl::json::object());
        }
#endif
    }

    void xlang_interpreter::close_xlang()
    {
        if (xlangCreated && client && isConnected)
        {
            xlangCreated = false;
            client->Get("/devops/terminate");
            xlangCreated = false;
            isConnected = false;
        }
    }

    // nl::json interpreter::kernel_info_request_impl()
    // {

    //     const std::string  protocol_version = "5.3";
    //     const std::string  implementation = "xlang_kernel";
    //     const std::string  implementation_version = XEUS_XLANG_VERSION;
    //     const std::string  language_name = "xlang";
    //     const std::string  language_version = "1.0.0";
    //     const std::string  language_mimetype = "text/x-xlang";;
    //     const std::string  language_file_extension = "x";;
    //     const std::string  language_pygments_lexer = "";
    //     const std::string  language_codemirror_mode = "";
    //     const std::string  language_nbconvert_exporter = "";
    //     const std::string  banner = "xlang_kernel";
    //     const bool         debugger = false;
    //     const nl::json     help_links = nl::json::array();

    //     return xeus::create_info_reply(
    //         protocol_version,
    //         implementation,
    //         implementation_version,
    //         language_name,
    //         language_version,
    //         language_mimetype,
    //         language_file_extension,
    //         language_pygments_lexer,
    //         language_codemirror_mode,
    //         language_nbconvert_exporter,
    //         banner,
    //         debugger,
    //         help_links
    //     );
    // }

}
